package org.kolonitsky.alexey.sound
{
    import flash.events.ErrorEvent;
    import flash.events.Event;
    import flash.events.EventDispatcher;
    import flash.events.IOErrorEvent;
    import flash.events.ProgressEvent;
    import flash.events.TimerEvent;
    import flash.media.ID3Info;
    import flash.media.Sound;
    import flash.media.SoundChannel;
    import flash.media.SoundLoaderContext;
    import flash.media.SoundTransform;
    import flash.net.URLRequest;
    import flash.system.LoaderContext;
    import flash.utils.Timer;
    
    import org.kolonitsky.alexey.events.MusicTrackEvent;
    
    
    [Event(name="ready", type="ru.vicman.photogames.events.MusicTrackEvent")]
    
    [Event(name="playComplete", type="ru.vicman.photogames.events.MusicTrackEvent")]
    
    [Event(name="play", type="ru.vicman.photogames.events.MusicTrackEvent")]
    
    [Event(name="stop", type="ru.vicman.photogames.events.MusicTrackEvent")]
    
    [Event(name="updatePosition", type="ru.vicman.photogames.events.MusicTrackEvent")]
    
    [Event(name="loading", type="ru.vicman.photogames.events.MusicTrackEvent")]
    
    [Event(name="playStatus", type="ru.vicman.photogames.events.MusicTrackEvent")]
    
    
    
    /**
     *
     */
    public class MusicTrack extends EventDispatcher
    {
        public static const UPDATE_POSITION_INTERVAL:Number = 25;
        
        public function MusicTrack(sound:Sound=null):void
        {
            super();
            _sound = sound;
            _soundTransform = new SoundTransform();
            updatePositionTimer = new Timer(UPDATE_POSITION_INTERVAL);
            updatePositionTimer.addEventListener(TimerEvent.TIMER, updatePositionTimer_timerHandler);
        }
        
        
        //----------------------------------------------------------------------
        //
        // Methods
        //
        //----------------------------------------------------------------------
        
        //------------------------------
        // Playback
        //------------------------------
        
        /** Paly sound
         */ 
        public function play(sound:Sound=null):void
        {
            if (sound) updateSound(sound);
            if (_isPlay) _channel.stop();
            if (_sound)
            {
                isPlay = true;
                _channel = _sound.play(_position, 0, _soundTransform);
                _channel.addEventListener(Event.SOUND_COMPLETE, channel_soundCompleteHandler);
                updatePositionTimer.start();
                dispatchEvent(new MusicTrackEvent(MusicTrackEvent.PLAY));
            }
        }
        
        /** Stop sound
         */ 
        public function stop():void
        {
            pause();
            time = 0;
        }
        
        /** Pause playing
         */ 
        public function pause():void
        {
            if (_channel == null) return;
            isPlay = false;
            _channel.stop();
            updatePositionTimer.stop();
            dispatchEvent(new MusicTrackEvent(MusicTrackEvent.STOP));
        }
        
        /** Resume playing 
         */ 
        public function resume():void
        {
            play();
        }
        
        //------------------------------
        // mute
        //------------------------------
        
        private var muteVolume:Number = -1;
        
        public function mute():void
        {
            muteVolume = _soundTransform.volume;
            volume = 0.0;
        }
        
        public function unmute():void
        {
            volume = muteVolume;
            muteVolume = -1;
        }
        
        public function isMute():Boolean
        {
            return muteVolume >= 0.0
        }
        
        
        //------------------------------
        // load
        //------------------------------
        
        public function load(url:*, context:LoaderContext=null):void
        {
            var request:URLRequest = url is URLRequest ? url : new URLRequest(url);
            
            var snd:Sound = new Sound();
            updateSound(snd);
            snd.load(request, new SoundLoaderContext(1000, false));
        }
        
        
        //----------------------------------------------------------------------
        //
        // Properties
        //
        //----------------------------------------------------------------------
        
        //------------------------------
        // loadingTime
        //------------------------------
        
        /** Get loaded time
         *  @return miliseconds 
         */ 
        public function get loadingTime():Number
        {
            return _sound.length || 1;
        }
        
        
        //------------------------------
        // length
        //------------------------------
        
        private var _length:Number = 1.0;
        
        /** Music track length
         *  @return miliseconds
         */
        public function get length():Number
        {
            return _length;
        }
        
        
        //------------------------------
        // time
        //------------------------------
        
        private var _position:Number = 0.0;
        
        /** Current playing position in time.
         *  @param miliseconds
         */ 
        public function set time(value:Number):void
        {
            if (_position == value) return;
            _position = value;
            if (isPlay)
                play();
            else
                dispatchEvent(new MusicTrackEvent(MusicTrackEvent.UPDATE_POSITION));
        }
        
        /** Get playhead time
         *  @return miliseconds
         */
        public function get time():Number
        {
            return _position;
        }
        
        
        //------------------------------
        // isPlay
        //------------------------------
        
        private var _isPlay:Boolean = false;
        
        [Bindable(event="musicTrackChangePlayStatus")]
        public function set isPlay(value:Boolean):void
        {
            if (_isPlay == value) return;
            
            _isPlay = value
            
            dispatchEvent(new Event("musicTrackChangePlayStatus"));
        }
        
        public function get isPlay():Boolean
        {
            return _isPlay;
        }
        
        
        //------------------------------
        // volume
        //------------------------------
        
        private var _soundTransform:SoundTransform;
        
        public function set volume(value:Number):void
        {
            if (volume == _soundTransform.volume) return;
            
            _soundTransform = new SoundTransform(value);
            _channel.soundTransform = _soundTransform;
        }
        
        public function get volume():Number
        {
            return _soundTransform.volume;
        }
        
        
        //------------------------------
        // loop
        //------------------------------
        
        private var _loop:Boolean = false;
        
        public function set loop(value:Boolean):void
        {
            _loop = value;
        }
        
        public function get loop():Boolean
        {
            return _loop;
        }
        
        
        //------------------------------
        // url
        //------------------------------
        
        private var _url:String;
        
        /** Url to sound. Sound start load after set url value.
         */ 
        public function set url(value:String):void
        {
            if (_url == value) return;
            _url = value;
            
            var snd:Sound = new Sound(new URLRequest(url), new SoundLoaderContext(1000, false));
            updateSound(snd);
        }
        
        public function get url():String
        {
            return _url;
        }
        
        
        //----------------------------------------------------------------------
        //
        // Private logic
        //
        //----------------------------------------------------------------------
        
        private var _sound:Sound;
        private var _channel:SoundChannel;

        private var updatePositionTimer:Timer;
        
        private function updateSound(snd:Sound=null):void
        {
            stop();
            
            if (_sound)
            {
                _sound.removeEventListener(Event.COMPLETE, sound_completeHandler);
                _sound.removeEventListener(ProgressEvent.PROGRESS, sound_progressHandler);
                _sound.removeEventListener(Event.ID3, sound_id3Handler);
                _sound.removeEventListener(IOErrorEvent.IO_ERROR, sound_ioErrorHandler);
            }
            
            _sound = snd || new Sound();
            _sound.addEventListener(Event.COMPLETE, sound_completeHandler);
            _sound.addEventListener(ProgressEvent.PROGRESS, sound_progressHandler);
            _sound.addEventListener(Event.ID3, sound_id3Handler);
            _sound.addEventListener(IOErrorEvent.IO_ERROR, sound_ioErrorHandler);
            _sound.addEventListener(Event.OPEN, function ():void {trace("INF: MusicTrack OPEN")});
            
            _length = _sound.length;
        }
        
        private function sound_completeHandler(event:Event):void
        {
            dispatchEvent(new MusicTrackEvent(MusicTrackEvent.READY));
        }
        
        private function sound_progressHandler(event:ProgressEvent):void
        {
            _length = event.bytesTotal / (event.bytesLoaded / loadingTime) 
            //trace("INF: MusicTrack Load: " + event.bytesLoaded + " / " + event.bytesTotal + ", " + loadingTime)
            dispatchEvent(new MusicTrackEvent(MusicTrackEvent.LOADING));
        }
        
        private function sound_ioErrorHandler(event:IOErrorEvent):void
        {
            trace("ERR: MusicTrack I/O Error");
            _sound = null;
            dispatchEvent(new ErrorEvent(ErrorEvent.ERROR));
        }
        
        private function sound_id3Handler(event:Event):void
        {
            trace("INF: MusicTrack has Loaded ID3 tags");
            try
            {
                var obj:ID3Info = _sound.id3;
                trace("INF: album: " + obj.album);
                trace("INF: artist: " + obj.artist);
                trace("INF: comment: " + obj.comment);
                trace("INF: genre: " + obj.genre);
                trace("INF: songName: " + obj.songName);
                trace("INF: track: " + obj.track);
                trace("INF: year: " + obj.year);
            }
            catch (error:Error)
            {
                trace("ERR: " + error.message);
            }
        }
        
        private function updatePositionTimer_timerHandler(event:TimerEvent):void
        {
            _position = _channel.position
            //trace("INF: MusicTrack POS: " + _position.toFixed() + " / " + _length.toFixed())
            dispatchEvent(new MusicTrackEvent(MusicTrackEvent.UPDATE_POSITION))
        }
        
        private function channel_soundCompleteHandler(event:Event):void
        {
            if (loop)
            {
                time = 0.0;
            }
            else
            {
                stop();
                dispatchEvent(new MusicTrackEvent(MusicTrackEvent.COMPLETE));
            }
        }

    }
}